#include "DB.h"
#include "DBPageManager.h"
#include "DBError.h"
#include <stdio.h>

int getMetaInfo (char *tableName, char *colName, int *offset, int *rowsize, int *coltype) {
	int i;
	char *dbffilename = malloc (50);
	if (dbffilename == NULL ) return -1;
	strcpy (dbffilename, tableName);
	strcat (dbffilename, ".dbf");
	FILE *fp;
	fp = fopen(dbffilename, "rb");
	if (fp == NULL) return -1;
	col *colDef = malloc (sizeof (col));
	if (colDef == NULL) return -1;
	unsigned long recordCount;
	int colCount;
	*offset = -1;
	fread (&recordCount, sizeof(recordCount), 1, fp);
	fread (&colCount, sizeof (recordCount), 1, fp);
	*rowsize = sizeof(unsigned long);
	int len = 0;
	for (i = 0; i < colCount; i++) {
		fread(colDef, sizeof(col), 1, fp);
		if (strcmp (colDef->name, colName) == 0){
			*offset = *rowsize;
			*coltype = colDef->type;
			if (colDef->type == DATA_CHAR) len = colDef->size;
		}
		*rowsize += sizeof (char);
		if (colDef->type == DATA_VCHAR) *rowsize += sizeof (int);
                *rowsize += colDef->size;
	}
	fclose (fp);
	free (dbffilename);
	free (colDef);
	return len;
}

int incret(whereLoc *iloc) {
	if (iloc == NULL) return 0;
	DB_PAGE *buf;
	iloc->rowid++;
	if (DBGetPage(iloc->fileid, iloc->pageid, &buf) != DB_OK) return 0;
	if (DBPageUsed(iloc->fileid, iloc->pageid) != DB_OK) return 0;
	int orig_pageid = iloc->pageid;
	if (buf->pgheader.slotCnt <= iloc->rowid){
		iloc->rowid= 0;
		iloc->pageid ++;
		if (!DBInvalidPageID(iloc->fileid, iloc->pageid)) {
			iloc->pageid = 0;
			if (DBPageRelease(iloc->fileid, orig_pageid, FALSE) != DB_OK) {
				DBDisposePage(iloc->fileid, orig_pageid);
				return 0;
			}
			if (DBDisposePage(iloc->fileid, orig_pageid) != DB_OK) return 0;
			return incret(iloc->nextLoc);
		}
	}
	if (DBPageRelease(iloc->fileid, orig_pageid, FALSE) != DB_OK) {
		DBDisposePage(iloc->fileid, orig_pageid);
		return 0;
	}
	if (DBDisposePage(iloc->fileid, orig_pageid) != DB_OK) return 0;
	return 1;
}

struct whereLoc *copyLoc(whereLoc *loc){
	whereLoc *ret = NULL;
	if (loc == NULL) return NULL;
	if ((ret= (whereLoc*) malloc (sizeof(whereLoc))) == 0) return NULL;
	ret->tableName = malloc (50);
	if (ret->tableName == NULL) return NULL;
	ret->fileid = loc->fileid;
	ret->pageid = loc->pageid;
	ret->rowid = loc->rowid;
	strcpy(ret->tableName, loc->tableName);
	ret->nextLoc = copyLoc(loc->nextLoc);
	return ret;
}

int getloc(whereLoc *temploc, int fileid, int *pageid, int * rowid){
	while (temploc != NULL) {
		if (temploc->fileid == fileid) {
			*pageid = temploc->pageid;
			*rowid = temploc->rowid;
			return 0;
		}
		temploc = temploc->nextLoc;
	}
	return -1;
}

int cmpint(int l, int r, int op) {
	switch (op) {
		case OP_EQ: 
			return (l == r);
			break;
		case OP_GE: 
			return (l >= r);
			break;
		case OP_GT: 
			return (l > r);
			break;
		case OP_LE: 
			return (l <= r);
			break;
		case OP_LT: 
			return (l < r);
			break;
		case OP_NE: 
			return (l != r);
			break;
	}
	return FALSE;
}

int cmpstr(char *l, char *r, int op) {
	switch (op) {
		case OP_EQ: 
			return (strcmp(l, r) == 0);
			break;
		case OP_GE: 
			return (strcmp(l, r) >= 0);
			break;
		case OP_GT: 
			return (strcmp(l, r) > 0);
			break;
		case OP_LE: 
			return (strcmp(l, r) <= 0);
			break;
		case OP_LT: 
			return (strcmp(l, r) < 0);
			break;
		case OP_NE: 
			return (strcmp(l, r) != 0);
			break;
	}
	return FALSE;
}


int freeloc (whereLoc *fl){
	if (fl->nextLoc!= NULL) freeloc (fl->nextLoc);
	free (fl->tableName);
	DBCloseFile(fl->fileid);
	free (fl);
	return 0;

}

int freeret (whereRet *fr){
	if (fr->nextRet != NULL) freeret (fr->nextRet);
	freeloc (fr->locations);
	free (fr);
	return 0;
}

int printloc (whereLoc *temp)
{
	if (temp == NULL) return TRUE;
	int i;
	char *dbffilename = malloc (50);
	if (dbffilename == NULL ) return -1;
	strcpy (dbffilename, temp->tableName);
	strcat (dbffilename, ".dbf");
	FILE *fp;
	fp = fopen(dbffilename, "rb");
	if (fp == NULL) return -1;
	col *colDef = malloc (sizeof (col));
	if (colDef == NULL) return -1;
	unsigned long recordCount;
	int colCount;
	fread (&recordCount, sizeof(recordCount), 1, fp);
	fread (&colCount, sizeof (recordCount), 1, fp);
	int rowsize = sizeof(unsigned long);
	for (i = 0; i < colCount; i++) {
		fread(colDef, sizeof(col), 1, fp);
		rowsize += sizeof (char);
		if (colDef->type == DATA_VCHAR) rowsize += sizeof (int);
		rowsize += colDef->size;
	}
	fclose (fp);
	free (dbffilename);
	free (colDef);
	DB_PAGE *buf;
	int error = DBGetPage (temp->fileid, temp->pageid, &buf);
	if (error < 0) 
	{
		DBPrintError(error);
		return -1;
	}
	DBPageUsed (temp->fileid, temp->pageid);
	unsigned long index;
	memcpy (&index, &(buf->pgdata[rowsize*temp->rowid]), sizeof (unsigned long));
	if (index == -1)
	{
		DBPageRelease(temp->fileid, temp->pageid,FALSE);
		DBDisposePage(temp->fileid, temp->pageid);
		return FALSE;
	}
	DBPageRelease(temp->fileid, temp->pageid,FALSE);
	DBDisposePage(temp->fileid, temp->pageid);
	return printloc(temp->nextLoc);
}

int Where (char **tables, int numofTable, whereCond *wherec, int numofCond, whereRet **ret)
{
	int i;
	if (numofTable < 1) return -1;
	*ret = NULL;			//ret head
	whereRet *tail = NULL;		//ret tail
	whereRet *tempRet;		//ret itrator

	if ((tempRet = malloc (sizeof (whereRet))) == NULL) return -1;
	tempRet->locations = NULL;
	tempRet->nextRet = NULL;
	whereLoc *loctail = tempRet->locations;

	//init ret itrator location

	for (i = 0; i < numofTable; i++) {
		whereLoc *temploc;
		if ((temploc= (whereLoc*)malloc (sizeof(whereLoc))) == 0) return -1;
		temploc->fileid = DBFileIsOpen(tables[i]);
		if (temploc->fileid == -1) temploc->fileid = DBOpenFile(tables[i]);
		temploc->pageid = 0;
		temploc->rowid=0;
		temploc->tableName = malloc (50);
		strcpy (temploc->tableName, tables[i]);
		temploc->nextLoc = NULL;


		if (loctail == NULL) {
			tempRet->locations = temploc;
			loctail = tempRet->locations;
		}
		else {
			loctail->nextLoc = temploc;
			loctail = loctail->nextLoc;
		}
	}

	tempRet->locations->rowid = -1;
	if (wherec != NULL) wherec[0].conOp = CON_AND;

	while (incret (tempRet->locations)){ 	//inc location rowid -> pageid -> fileid -> nextfile rowid -> ...
		int flag = printloc (tempRet->locations);
		if (flag)
			for (i = 0; i < numofCond; i++) {
				wherec[i].boolBefore = flag;
				struct whereCond tempCond = wherec[i];
				if (tempCond.lType == VALUE_TYPE_VAR)
				{
					if (tempCond.lTableName == NULL) tempCond.lTableName = tables[0];
					int lfileid, lpageid, lrowid;
					lfileid = DBFileIsOpen (tempCond.lTableName);
					getloc (tempRet->locations, lfileid, &lpageid, &lrowid);
					int llen, loffset, lrowsize, ltype;
					llen = getMetaInfo (tempCond.lTableName, tempCond.lColName, &loffset, &lrowsize, &ltype);
					DB_PAGE *buf;
					int error = DBGetPage (lfileid, lpageid, &buf);
					if (error < 0) 
					{
						DBPrintError(error);
						return -1;
					}
					DBPageUsed (lfileid, lpageid);
					unsigned long lindex;
					memcpy (&lindex, &(buf->pgdata[lrowsize*lrowid]), sizeof (unsigned long));
					if (lindex == -1)
					{
						flag = FALSE;
						DBPageRelease(lfileid, lpageid,FALSE);
						DBDisposePage(lfileid, lpageid);
						break;
					}
					if (((char)buf->pgdata[lrowsize*lrowid+loffset]) == 1)
					{
						if (ltype == DATA_INT) 
						{
							tempCond.lType = VALUE_TYPE_INT;
							memcpy (&tempCond.lInt, &(buf->pgdata[lrowsize*lrowid+loffset+1]), sizeof (int));
						}
						else if (ltype == DATA_CHAR)
						{
							tempCond.lType = VALUE_TYPE_STR;
							tempCond.lChar = malloc (llen+1);
							memcpy (tempCond.lChar, &(buf->pgdata[lrowsize*lrowid+loffset+1]), llen);
							*(tempCond.lChar+llen) = 0;
						}
						else
						{
							tempCond.lType = VALUE_TYPE_STR;
							memcpy (&llen, &(buf->pgdata[lrowsize*lrowid+loffset+1]), sizeof (int));
							tempCond.lChar = malloc (llen+1);
							memcpy (tempCond.lChar, &(buf->pgdata[lrowsize*lrowid+loffset+1+sizeof(int)]), llen);
							*(tempCond.lChar+llen) = 0;
						}
					}
					else tempCond.lType = VALUE_TYPE_NULL;
					DBPageRelease (lfileid, lpageid, FALSE);
					DBDisposePage (lfileid, lpageid);
				}
				if (tempCond.rType == VALUE_TYPE_VAR)
				{
					if (tempCond.rTableName == NULL) tempCond.rTableName = tables[0];
					int rfileid, rpageid, rrowid;
					rfileid = DBFileIsOpen (tempCond.rTableName);
					getloc (tempRet->locations, rfileid, &rpageid, &rrowid);
					int rlen, roffset, rrowsize, rtype;
					rlen = getMetaInfo (tempCond.rTableName, tempCond.rColName, &roffset, &rrowsize, &rtype);

					DB_PAGE *buf;
					int error = DBGetPage (rfileid, rpageid, &buf);
					if (error < 0) 
					{
						DBPrintError(error);
						return -1;
					}
					DBPageUsed (rfileid, rpageid);
					unsigned long rindex;
					memcpy (&rindex, &(buf->pgdata[rrowsize*rrowid]), sizeof (unsigned long));
					if (rindex == -1)
					{
						flag = FALSE;
						DBPageRelease(rfileid, rpageid, FALSE);
						DBDisposePage(rfileid, rpageid);
						break;
					}
					if (buf->pgdata[rrowsize*rrowid+roffset] == 1)
					{
						if (rtype == DATA_INT) 
						{
							tempCond.rType = VALUE_TYPE_INT;
							memcpy (&tempCond.rInt, &(buf->pgdata[rrowsize*rrowid+roffset+1]), sizeof (int));
						}
						else if (rtype == DATA_CHAR)
						{
							tempCond.rType = VALUE_TYPE_STR;
							tempCond.rChar = malloc (rlen+1);
							memcpy (tempCond.rChar, &(buf->pgdata[rrowsize*rrowid+roffset+1]), rlen);
							*(tempCond.rChar+rlen) = 0;
						}
						else
						{
							tempCond.rType = VALUE_TYPE_STR;
							memcpy (&rlen, &(buf->pgdata[rrowsize*rrowid+roffset+1]), sizeof (int));
							tempCond.rChar = malloc (rlen+1);
							memcpy (tempCond.rChar, &(buf->pgdata[rrowsize*rrowid+roffset+1+sizeof(int)]), rlen);
							*(tempCond.rChar+rlen) = 0;
						}
					}
					else tempCond.rType = VALUE_TYPE_NULL;
					DBPageRelease (rfileid, rpageid, FALSE);
					DBDisposePage (rfileid, rpageid);
				}

				if (tempCond.op == OP_ISNIL && (tempCond.lType == VALUE_TYPE_NULL || tempCond.rType == VALUE_TYPE_NULL)) flag = TRUE;
				else if (tempCond.lType == tempCond.rType)
				{
					if (tempCond.lType == VALUE_TYPE_INT) flag = cmpint (tempCond.lInt, tempCond.rInt, tempCond.op);
					else if (tempCond.lType == VALUE_TYPE_STR) flag = cmpstr (tempCond.lChar, tempCond.rChar, tempCond.op);
					else flag = FALSE;
				}
				else flag = FALSE;

				if (tempCond.conOp == CON_OR) flag |= tempCond.boolBefore;
				else flag &= tempCond.boolBefore;

				if (wherec[i].lType == VALUE_TYPE_VAR && tempCond.lType == VALUE_TYPE_STR)  free (tempCond.lChar);
				if (wherec[i].rType == VALUE_TYPE_VAR && tempCond.rType == VALUE_TYPE_STR)  free (tempCond.rChar);
			}

		if (flag){ 
			if (*ret == NULL){
				*ret = tempRet;
				tail = *ret;
			}
			else {
				tail->nextRet = tempRet;
				tail = tail->nextRet;
			}
			if ((tempRet = (whereRet*)malloc (sizeof (whereRet))) == NULL) return -1;
			tempRet->nextRet = NULL;
			tempRet->locations = copyLoc(tail->locations);
		}
	}
	freeret (tempRet);
	return 0;
}

